package cn.zhengwenzhou.system.aop;

import cn.zhengwenzhou.system.annotation.SysLog;
import cn.zhengwenzhou.system.entity.SystemLog;
import cn.zhengwenzhou.system.entity.User;
import cn.zhengwenzhou.system.service.SystemLogService;
import cn.zhengwenzhou.system.service.UserService;
import cn.zhengwenzhou.system.utils.IPUtils;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 使用注解的方式配置Aop切点
 * 在需要记录Aop的方法前添加注解，例如：
 *
 * @SysLog(module = "用户管理",methods = "添加用户")
 */
@Aspect
@Component
public class SystemLogAspect
{
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	private LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
	
	@Autowired
	private SystemLogService systemLogServiceImpl;
	
	@Autowired
	private UserService userServiceImpl;
	
//	@Pointcut("execution(* cn.zhengwenzhou.*.controller..*.*(..))")
//	@Pointcut("execution(* cn.zhengwenzhou.*.controller..*.*(..))")
	@Pointcut("@annotation(cn.zhengwenzhou.system.annotation.SysLog)")
	public void logAspect()
	{
	}
	
	@Before("logAspect()")
	public void before()
	{
		//第二步：方法执行之前
		logger.info("Before", "--------------Before--------------");
	}
	
	@Around("logAspect()")
	public Object around(ProceedingJoinPoint p) throws Throwable
	{
		//第一步：开始环绕通知
		logger.info("Around", "--------------Around--------------进入环绕通知");
		long startTime = System.currentTimeMillis();
		
		//第三步：进入方法(执行方法)
		Object obj = p.proceed();
		
		long endTime = System.currentTimeMillis();
		MethodSignature signature = (MethodSignature) p.getSignature();
		String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
		logger.info("Around", "目标方法 : " + methodName);
		logger.info("Around", "运行耗时 : " + (endTime - startTime) + "ms");
		logger.info("Around", "--------------Around--------------结束方法调用");
		
		//第四步：方法执行完毕（若方法发生异常，则走不到这里）
		return obj;
	}
	
	@After("logAspect()")
	public void after(JoinPoint point)
	{
		//第五步：方法执行完毕之后
		logger.info("After", "--------------After--------------开始最终通知");
		logger.info("After", "目标方法 : " + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());
		logger.info("After", "参数 : " + Arrays.toString(point.getArgs()));
		logger.info("After", "被织入的对象 : " + point.getTarget());
		logger.info("After", "--------------After--------------结束最终通知");
	}
	
	@AfterReturning(pointcut = "logAspect()", returning = "returnValue")
	public void afterReturning(JoinPoint point, Object returnValue)
	{
		//第六步：方法执行完毕之后的结果
		logger.info("AfterReturning", "--------------AfterReturning--------------开始返回值后通知");
		logger.info("AfterReturning", "目标方法 : " + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());
		logger.info("AfterReturning", "参数 : " + Arrays.toString(point.getArgs()));
		logger.info("AfterReturning", "返回值 : " + returnValue);
		logger.info("AfterReturning", "--------------AfterReturning--------------结束返回值后通知");
		
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes sra = (ServletRequestAttributes) ra;
		HttpServletRequest request = sra.getRequest();
		User user = userServiceImpl.getSessionUser(request);
		try
		{
			Map<String, Object> map = this.getMethodDescription(point);
			SystemLog systemLog = new SystemLog();
			String targetName = point.getSignature().getDeclaringTypeName();
			String methodName = point.getSignature().getName();
			systemLog.setMethod(targetName + "." + methodName);
			systemLog.setParams(map.get("args").toString());
			if(user != null)
			{
				systemLog.setUserId(user.getId());
				systemLog.setUsername(user.getUsername());
			}
			systemLog.setIp(IPUtils.getRemoteIp(request));
			systemLog.setStatus("success");
			systemLog.setReturnValue(returnValue.toString());
			systemLog.setCreateTime(new Date().getTime());
			systemLog.setAnnotationMethod(map.get("methods").toString());
			systemLog.setAnnotationModule(map.get("module").toString());
			systemLog.setAnnotationDesc(map.get("description").toString());
			systemLog.setExceptionCode(null);
			systemLog.setExceptionDetail(null);
			systemLogServiceImpl.insert(systemLog);
		} catch(Exception e)
		{
			e.printStackTrace();
		}
		
	}
	
	@AfterThrowing(pointcut = "logAspect()", throwing = "e")
	public void afterThrowing(JoinPoint point, Throwable e)
	{
		//第六步：方法执行过程中抛出异常会跳进来
		logger.info("AfterThrowing", "--------------AfterThrowing--------------开始异常通知");
		logger.info("AfterThrowing", "异常信息 : " + e.getMessage());
		logger.info("AfterThrowing", "--------------AfterThrowing--------------结束异常通知");
		
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes sra = (ServletRequestAttributes) ra;
		HttpServletRequest request = sra.getRequest();
		User user = userServiceImpl.getSessionUser(request);
		try
		{
			Map<String, Object> map = this.getMethodDescription(point);
			SystemLog systemLog = new SystemLog();
			String targetName = point.getSignature().getDeclaringTypeName();
			String methodName = point.getSignature().getName();
			systemLog.setMethod(targetName + "." + methodName);
			systemLog.setParams(map.get("args").toString());
			if(user != null)
			{
				systemLog.setUserId(user.getId());
				systemLog.setUsername(user.getUsername());
			}
			systemLog.setIp(IPUtils.getRemoteIp(request));
			systemLog.setStatus("failed");
			systemLog.setReturnValue(null);
			systemLog.setCreateTime(new Date().getTime());
			systemLog.setAnnotationMethod(map.get("methods").toString());
			systemLog.setAnnotationModule(map.get("module").toString());
			systemLog.setAnnotationDesc(map.get("description").toString());
			systemLog.setExceptionCode(e.getClass().getName());
			systemLog.setExceptionDetail(e.getMessage());
			systemLogServiceImpl.insert(systemLog);
		} catch(Exception ex)
		{
			ex.printStackTrace();
		}
		
	}
	
	/**
	 * 获取注解中对方法的描述信息 用于SysLog注解
	 *
	 * @param joinPoint 切点
	 * @return 方法描述
	 * @throws Exception
	 */
	public Map<String, Object> getMethodDescription(JoinPoint joinPoint) throws Exception
	{
		Map<String, Object> map = new HashMap<String, Object>();
		String targetName = joinPoint.getTarget().getClass().getName();
		String methodName = joinPoint.getSignature().getName();
		Object[] arguments = joinPoint.getArgs();
		Class<?> targetClass = Class.forName(targetName);
		Method[] methods = targetClass.getMethods();
		for(Method method : methods)
		{
			if(method.getName().equals(methodName))
			{
				Class[] clazzs = method.getParameterTypes();
				if(clazzs.length == arguments.length)
				{
					map.put("module", method.getAnnotation(SysLog.class).module());
					map.put("methods", method.getAnnotation(SysLog.class).methods());
					map.put("description", method.getAnnotation(SysLog.class).description());
					map.put("args", this.getArgs(method, arguments));
					break;
				}
			}
		}
		return map;
	}
	
	/**
	 * 获取拦截方法参数
	 *
	 * @param method
	 * @param arguments
	 * @return
	 */
	private String getArgs(Method method, Object[] arguments)
	{
		String params[] = parameterNameDiscoverer.getParameterNames(method);
		JSONObject jsonObject = new JSONObject();
		for(int i = 0; i < params.length; i++)
		{
			jsonObject.put(params[i], arguments[i]);
		}
		return jsonObject.toJSONString();
	}
}
